#include "messages.h"

/**
 * Main message function. Calls other functions after determining type of message.
 *
 * @param message The incoming message.
 * @param outstandingRequestsList The list of filenames that qualify as outstanding requests.
 * @param senderInfo Contains the sender's ip address and port number.
 */
void handleMessage(unsigned char * message, struct list * requestList, struct addressPort senderInfo) {

  // If first byte of the message is hexadecimal value 0xDD (221) it is a data message.
  if (message[0] == 0xDD) {
	
    //Handle data messages
    handleDataMessage(message, senderInfo);
  }

  // If first byte of the message is hexadecimal value of 0xCC (204) it is a control message.
  else if (message[0] == 0xCC) {

    //Handle control messages
    handleControlMessage(message, requestList, senderInfo);
  }

  // Handle case where message is found to be neither
  else {
    perror("Message is neither data nor control message.");
  }
}

/**
 * Handles data message types. The p2p program will only act when receiving a data message
 * that corresponds to an outstanding request. If so, p2p will keep a record in its data cache
 * and save the file data into its local directory. Then it will update its content directory
 * with peer & filename if not already present. Finally, it'll remove the request from the list
 * of outstanding requests.
 *
 * @param message The incoming data message.
 * @param senderInfo Contains the sender's ip address and port number. 
 */
void handleDataMessage(unsigned char * message, struct addressPort senderInfo) {
	
  unsigned char filename[MAX_NAME];
  memset(filename, 0, sizeof(filename));
  int messageIndex = 0;

  // Loop variable
  int i;

  // Determine the filename provided by the data message.
  strcpy((char *) filename, (char *) message+1);

  // Where we left off parsing through the message.
  // Positions pointer to point to character after the terminating zero after
  // the name.
  messageIndex += strlen((char *) filename)+2;

  int isOutStandingRequest = false;
  i = 0;
	
  // Search through the list of outstanding requests to determine if
  // data message corresponds to such a request.
	if (searchList(&outstandingRequestsList, filename, 3) == true) {	

		printf("\nCONTENT: %s\nFOUND\n", filename);
	
		isOutStandingRequest = true;

		// The length of the file content is 2-bytes long so compute using two char elements of the message.
    // Create a copy of the file content for data cache entry.
    // Handle multi-byte content length
    short int fileSize;
    short int * fileSizePtr;
    fileSizePtr = (short int *) &message[messageIndex];
    fileSize = ntohs(*fileSizePtr);
      
    unsigned char content[fileSize];
    messageIndex += 2;
		
		if (debug) {
			printf("fileSize is %d\n", fileSize);
		}
		
		writeToFile(&message[messageIndex], fileSize, filename);
		messageIndex = messageIndex + fileSize;
		
    // Was an outstanding request file and was saved to local directory.
    // Must update the data cache to reflect the addition of a new file to local directory.
    updateDataCache(filename, content, fileSize);
		if (debug) {
			printf("handleDataMessage function: After updateDataCache function call\n");  
		}
		// Update the content directory by either adding an entry or shifting the entries to reflect
    // LRU setup.
    updateContentDirectory(filename, senderInfo);
		if (debug) {
			printf("handleDataMessage function: After updateContentDirectory function call\n");  
		}		
		
		// The outstanding request has been resolved, it was removed as part of the 
		// searchOutstandingReqList() function.
    return;
  }

  // Report the event of discarding a data message.
  if (isOutStandingRequest == false) {
    printf("p2p has discarded a request for file: %s\n", filename);
    printf("Did not correspond to an outstanding request.\n");
    return;
  }
}

/** 
 * Updates the data cache. If an entry is a duplicate, will update only
 * the order of the entry within the cache to reflect a LRU setup, such
 * that the least used entry is at the head or beginning of the list and the 
 * duplicate entry will be at the tail. Otherwise, adds an entry to
 * the data cache.
 *
 * @param filename The name associated with the content of the message.
 * @param content The data of the file.
 * @param fileSize The amount of byte data in content.
 */
void updateDataCache(unsigned char * filename, unsigned char * content, int fileSize) {
 
	// Test code: Displays the dataCacheList before update.
	if (debug) {
		printf("\n");
		printf("updateDataCache function: dataCacheList size before update -> %d\n", dataCacheList.size);
		struct node * it1 = createIterator(dataCacheList);
		while (hasNext(it1)) {
			next(&it1);
			struct dataCache * dc1 = (struct dataCache *) it1->payload;
			printf("updateDataCache function: dataCacheItem %s\n", dc1->name);
		}
		printf("\n");
	}
 
  int isInDataCache = false;
	
	struct node * iterator = createIterator(dataCacheList);
	struct node * previousNode;
	struct dataCache * tempDataCache;
	
	while (hasNext(iterator)) {
	
    previousNode = next(&iterator);
		tempDataCache = (struct dataCache *) iterator->payload;
		
		if(strcmp((char *) filename, tempDataCache->name) == 0) {
			
			if (debug) {
        //printf("updateDataCache function: The filename is %s, dataCache name is %s\n", filename, tempDataCache->name);
      }
			
			// File already in the data cache, no need to add but have to update use time relevance.
		  isInDataCache = true;
			
			// If at the end of the list, then LRU algorithm already reflected, no need further action.
			if (iterator == dataCacheList.tail || dataCacheList.size == 1){
			  break;
			}
			// Handle for case such that the node to sort is the head node.			
			else if (iterator == dataCacheList.head){
					dataCacheList.head = iterator->next;
					dataCacheList.tail->next = iterator;
					dataCacheList.tail = iterator;
					dataCacheList.tail->next = NULL;
			}
			// Otherwise handle all other cases.
			else {
					previousNode->next = iterator->next;
					dataCacheList.tail->next = iterator;
					dataCacheList.tail = iterator;
					dataCacheList.tail->next = NULL;
			}			
		}
	}
	
  // No record of file in data cache, must add it.
  if (isInDataCache == false) {
		struct node * newDataCacheItem = createCacheItem((char *)filename, (char *) content, fileSize);
		push(&dataCacheList, newDataCacheItem);
  }
	
	// Test code: Displays the dataCacheList after update.
	if (debug) {
		printf("updateDataCache function: dataCacheList size after update -> %d\n", dataCacheList.size);
		struct node * it2 = createIterator(dataCacheList);
		while (hasNext(it2)) {
			next(&it2);
			struct dataCache * dc2 = (struct dataCache *) it2->payload;
			printf("updateDataCache function: dataCacheItem %s\n", dc2->name);
		}
		printf("\n");
	}
}

/**
 * Updates the content directory by adding a non-duplicate <peer, filename> pair
 * to the current content directory. Most recently used entry is at the end of the
 * list and the least at the head of the list. Maximum amount of entries is 16.
 *
 * @param filename The name associated with the content of the message.
 * @param senderInfo Contains the ip address and port number of the sender of the message.
 */
void updateContentDirectory(unsigned char * filename, struct addressPort senderInfo) { 

	struct node * tempContent = createContentDirItem(filename, senderInfo);	
	
	// Search through the current content directory to see if an entry with filename is already
  // recorded.
	if (searchContentDir(filename, senderInfo) == false) {
		push(&contentDirectoryList, tempContent);
	}
}

/**
 * Removes an outstanding request from the list after being handled.
 *
 * @param filename The outstanding request with filename is removed.
 */
void removeOutstandingRequest(unsigned char * filename) {

	// Searches for match and removes from list.
	searchList(&outstandingRequestsList, (char *) filename, 3);
}

/**
 * Handles control messages. Parses and redirects message to the
 * proper function to be handled.
 *
 * @param message Control messages to be parsed and passed.
 * @param outstandingRequestsList List of current outstanding requests.
 * @param senderInfo Contains the requester's ip address and port numbers.
 */
void handleControlMessage(unsigned char * message, struct list * outstandingRequestsList, struct addressPort senderInfo) {
  
	if (debug) {
    printf("This is a CONTROL message\n");
  }

  //Look at the second byte of the message (SELECTOR byte)
  switch (message[1]) {

    // LISTING message
    case 0x4C:
      if (debug) {
        printf("This is a LISTING message\n");
      }
			handleListingMessage(message, senderInfo);
      break;

    // REQUEST message
    case 0x52:
      if (debug) {
        printf("This is a REQUEST message\n");
      }
			handleRequestMessage(message, senderInfo);
      break;

    // TRY message
    case 0x54:
      if (debug) {
        printf("This is a TRY message\n");
      }
			handleTryMessage(message,senderInfo);
      break;

    default:
      error("Unreadable control message");
  }
}

/**
 * Handles listing messages.
 *
 * @param message The listing message to parse and add entries to content directory.
 * @param senderInfo Contains the requester's ip address and port numbers.
 */
void handleListingMessage (unsigned char * message, struct addressPort senderInfo){

  //Count is the number of items listed
  short count;

  //Points to the location in the message where the count is
  short * countPtr;

  //Looks at 3rd and 4th bytes of message for number of items listed
  void * ptr = (void *) message+2;
  countPtr = (short * ) ptr;

  //Assigns value to count (since received through network need to use htons)
  count = htons(*countPtr);

  //Maximum count is 58
  if (count > 58) {
    error("Count maximum is 58");
  }

  if (debug) {
    printf ("Listing Message COUNT of Files: %d\n", count);
  }

  //Loop variable
  int i;

  //String pointer to start of names (5th byte)
  ptr = (void * ) message+4;
  char * stringPtr = (char * ) ptr;

  //Temp node
  struct node * tempNode;

  //Temp content directory struct
  struct contentDir * tempContentDir;

  //Flag to denote if content directory already contains the entry
  int found;
  
  //Put filenames from listing into content directory
  for (i = 0; i < count; i ++){
  
    found = false;
    
    //Check if content already in content directory    
    //If already in list, make most recently used
    found = searchContentDir(stringPtr, senderInfo);
    
    //If not found, then create new directory item and push onto list
    //Create content directory item
    if (!found){
      tempNode = createContentDirItem (stringPtr, senderInfo);
      push(&contentDirectoryList, tempNode);
      
      //Debugging Statements
      if (debug) {
        printf("HandleListingMessage - Pushing from Listing Message into Content Dir: %s\n", stringPtr);
      }
      
      //Advance the pointer to the next name
      stringPtr += strlen(stringPtr) + 1;
    }
  }
}
